#include "config.h"

//purpose: parse unmap descriptors and perform unmap operation in lich, confrim to the standard SBC-4, SPC-5 and SAM-4.
//file created by zsy on 2017.09.10
//don't simply modify this code except you have fully read read above t10.org standard documents.

#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/types.h>
#include <netinet/tcp.h>

#include "iscsi.h"
#include "dbg.h"

struct scsi_data_descriptor {
		uint64_t sdd_lba;
		uint64_t sdd_blocks;
};

//parsing unmap descriptors and checking if valid. 
static int scsi_parse_unmap_descriptors(struct iscsi_cmd *cmd, struct scsi_data_descriptor **ppd, uint32_t *pd_size)
{
		int res = 0;
		uint8_t *address;
		int i, cnt, offset, descriptor_len, total_len;
		struct scsi_data_descriptor *pd;
		struct iscsi_scsi_cmd_hdr *req = cmd_scsi_hdr(cmd);

		address = (uint8_t *)malloc(cmd->pdu.datasize);

		mbuffer_get(&cmd->tio->buffer, address, cmd->pdu.datasize);

		total_len = cpu_to_be16(*((uint16_t *)&req->scb[7]));
		offset = 8;

		descriptor_len = cpu_to_be16(*((uint16_t *)&address[2]));

		DBUG("unmap, total_len %d, descriptor_len %d\r\n", total_len, descriptor_len);

		if (descriptor_len == 0)    //it isn't an error.
				goto out_put;

		if (unlikely((descriptor_len > (total_len - 8)) || ((descriptor_len % 16) != 0))) 
		{
				DERROR("Bad descriptor length: %d < %d - 8\r\n", descriptor_len, total_len);
				create_sense_rsp(cmd, ILLEGAL_REQUEST, 0x24, 0x00);
				
				goto out_put;
		}

		cnt = descriptor_len/16;
		if (cnt == 0)
				goto out_put;

		pd = malloc(cnt * sizeof(*pd));
		if (pd == NULL) {
				DERROR("Unable to malloc unmap %d descriptors\r\n", cnt+1);
				create_sense_rsp(cmd, ILLEGAL_REQUEST, 0x24, 0x00);
				
				goto out_put;
		}

		DBUG("cnt %d, pd %p\r\n", cnt, pd);

		i = 0;
		while ((offset - 8) < descriptor_len) {
				struct scsi_data_descriptor *d = &pd[i];

				d->sdd_lba = cpu_to_be64(*((uint64_t *)&address[offset])); 
				offset += 8;
				d->sdd_blocks = cpu_to_be32(*((uint32_t *)&address[offset]));
				offset += 8;
				
				DBUG("i %d, lba %lld, blocks %lld\r\n", i, (long long)d->sdd_lba, (long long)d->sdd_blocks);
				i++;
		}

		*ppd = pd;
		*pd_size = cnt;
		
		return 0;

out_put:
		
		free(address);

		return res;
}

//todo, unmap in lich is really slow, will (just maybe) cause iSCSI I/O timeout.
int disk_io_unmap(struct iscsi_cmd *cmd)
{
		struct scsi_data_descriptor *pd = NULL;
		struct scsi_data_descriptor *p;
		uint32_t count;

		DBUG("disk_io_unmap enter\r\n");

		int ret = scsi_parse_unmap_descriptors(cmd, &pd, &count);
		if(ret)
				return ret;

		p = pd;
		for(int i = 0;i<count;i++)
		{
				ret = tio_unmap(cmd, p->sdd_lba, p->sdd_blocks);

				p ++;
		}

		if(pd)
				free(pd);

		DBUG("disk_io_unmap end\r\n");
		
		return 0;
}